import datetime
import hashlib
import json
import os.path
import pathlib
import re
import shutil
import sys

import requests

mirrors = [
    "https://mirrors.cloud.tencent.com/nexus/repository/maven-public",
    "https://maven.aliyun.com/repository/public",
    "https://repo.huaweicloud.com/repository/maven",
]

components = [
    "kotlin-compiler-embeddable",
    "kotlin-gradle-plugin",
    "kotlin-annotation-processing-gradle",
    "kotlin-gradle-plugin-idea",
    "kotlin-stdlib",
    "kotlin-daemon-client",
    "kotlin-android-extensions",
    "kotlin-scripting-compiler-embeddable",
    "kotlin-gradle-plugin-api",
    "kotlin-daemon-embeddable",
    "kotlin-native-utils",
    "kotlin-scripting-compiler-impl-embeddable",
]

suffixes = [
    ".module",
    "-sources.jar",
    ".pom",
    ".jar",
]

home = pathlib.Path.home()
base_dir = os.path.join(home, ".gradle", "caches", "modules-2", "files-2.1")


def file_sha1(file_path: str) -> str:
    sha1 = hashlib.sha1()
    with open(file_path, 'rb') as f:
        while True:
            data = f.read(4096)
            if not data:
                break
            sha1.update(data)
    return re.sub("^0+", "", sha1.hexdigest())


class Component(object):
    def __init__(self, group: str, name: str, version: str):
        self.group = group
        self.name = name
        self.version = version
        self.files = {suffix: ComponentFile(self, suffix) for suffix in suffixes}
        self.check_exists()
        self.module = False

    def check_exists(self):
        path = self.path()
        if not os.path.exists(path):
            return
        dirs = list(filter(lambda x: os.path.isdir(os.path.join(path, x)), os.listdir(path)))
        for d in dirs:
            files = list(filter(lambda x: os.path.isfile(x) and file_sha1(x) == d, [os.path.join(path, d, f) for f in os.listdir(os.path.join(path, d))]))
            if len(files) == 0:
                continue
            file = files[0]
            for suffix in suffixes:
                if file.endswith(suffix) and (suffix != "-sources.jar" or not file.endswith("-sources.jar")):
                    self.files[suffix].exist = True
                    self.files[suffix].sha1 = d
                    if suffix == ".module":
                        self.with_module()
                    break

    def with_module(self):
        if ".module" not in self.files or not self.files[".module"].exist:
            return
        md = self.files[".module"]
        mdf = md.file()
        with open(mdf, 'r', encoding='utf-8') as f:
            data = json.load(f)
            if "variants" not in data:
                return
            jar_files = list(filter(lambda x: x["name"].startswith("runtimeElements") or x["name"].endswith("RuntimeElements"), data["variants"]))
            if len(jar_files) > 0:
                jar_file = jar_files[-1]
                self.files[".jar"].f = jar_file["files"][0]["url"]
                self.files[".jar"].sha1 = jar_file["files"][0]["sha1"]
            # source_files = list(filter(lambda x: x["name"].startswith("sourcesElements") or x["name"].endswith("SourcesElements"), data["variants"]))
            # if len(source_files) > 0:
            #     source_file = source_files[-1]
            #     self.files["-sources.jar"].f = source_file["files"][0]["url"]
            #     self.files["-sources.jar"].sha1 = source_file["files"][0]["sha1"]

    def path(self):
        return os.path.join(base_dir, self.group, self.name, self.version)

    def sub_file(self, suffix):
        return "{}-{}{}".format(self.name, self.version, suffix)

    def temp_file(self, suffix: str) -> str:
        return os.path.join(self.path(), self.sub_file(suffix))

    def not_exists_files(self):
        return list(filter(lambda x: not x.exist, self.files.values()))

    def is_completed(self):
        return len(self.not_exists_files()) == 0


class ComponentFile(object):
    def __init__(self, com: Component, suffix: str, sha1: str = None, exist: bool = False):
        self.com = com
        self.suffix = suffix
        self.sha1 = sha1
        self.exist = exist
        self.download = False
        self.f = None

    def temp_file(self):
        return os.path.join(self.com.path(), self.f) if self.f else self.com.temp_file(self.suffix)

    def file_name(self):
        return self.f or self.com.sub_file(self.suffix)

    def file(self):
        if self.sha1 is None:
            return self.temp_file()
        return os.path.join(self.com.path(), self.sha1, self.file_name())

    def url(self, mirror: str) -> str:
        return "/".join([mirror, self.com.group.replace(".", "/"), self.com.name, self.com.version, self.file_name()])


def sync_version(version: str):
    group = "org.jetbrains.kotlin"
    coms = [Component(group, c, version) for c in components]
    files = [f for c in coms for f in c.not_exists_files()]
    count = len(files)
    if count == 0:
        print("没有需要下载的文件。")
        return

    print("需要下载 {} 个文件".format(count))
    begin = datetime.datetime.now()
    finishes = list(filter(lambda x: x is not None, [download_file(file) for file in files]))
    end = datetime.datetime.now()
    cost = (end - begin).seconds
    print("完成下载 {} 个文件，耗时 {} 秒".format(len(finishes), cost))


def download_file(file: ComponentFile) -> str | None:
    if file.exist:
        return file.file()

    temp = file.temp_file()
    path = os.path.dirname(temp)
    if not os.path.exists(path):
        os.makedirs(path)

    if os.path.exists(temp):
        os.remove(temp)
    begin = datetime.datetime.now()
    for i in range(0, len(mirrors)):
        url = file.url(mirrors[i])
        try:
            with open(temp, 'wb') as f:
                response = requests.get(url, stream=True)
                if response.status_code == 200:
                    for chunk in response.iter_content(chunk_size=8192):
                        f.write(chunk)
                    file.download = True
                    break
                if response.status_code == 404:
                    return None
                print("失败[{}]: file={}, status={}, url={}".format(i, file.file_name(), response.status_code, url))
        except requests.exceptions.HTTPError | requests.exceptions.ChunkedEncodingError as e:
            print("失败[{}]: file={}, error={}, url={}".format(i, file.file_name(), e, url))
    if not file.download:
        print("下载失败: file={}".format(file.file_name()))
        if os.path.exists(temp):
            os.remove(temp)
        return None
    end = datetime.datetime.now()
    cost = (end - begin).seconds

    file.sha1 = file_sha1(temp)
    f = file.file()

    path = os.path.dirname(f)
    if not os.path.exists(path):
        os.makedirs(path)
    shutil.move(temp, f)
    file.exist = True

    if file.suffix == ".module":
        file.com.with_module()

    print("下载: {} 耗时 {} 秒".format(f, cost))
    return f


if __name__ == '__main__':
    if len(sys.argv) > 1:
        sync_version(sys.argv[1])
